package com.fowo.api.sys.rdp.impl;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fowo.api.common.util.ListUtils;
import com.fowo.api.sys.rdp.RelDataPermissionsService;
import com.fowo.api.sys.rdp.model.RelDataPermissions;
import com.fowo.api.sys.rdp.model.RelDataPermissionsJson;
import com.fowo.api.sys.rdp.model.SearchRelDataPermissions;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.sql.DataSource;
import java.io.InputStream;
import java.util.*;

/**
 * 关联数据权限服务实现
 * @author yl_ls
 */
@Slf4j
@Service
public class RelDataPermissionsServiceImpl implements RelDataPermissionsService, ApplicationContextAware {

    @Resource
    private RedisTemplate<String, Object> redisTemplate;
    @Resource
    private DataSource dataSource;

    private Map<String, SearchRelDataPermissions> allRelDataPermissions;
    private Map<String, List<String>> changeAopToCacheKeys;


    @Override
    public SearchRelDataPermissions getFormSearchParam(String searchParamClassName) {
        if (allRelDataPermissions == null) {
            loadJson();
        }
        return allRelDataPermissions.get(searchParamClassName);
    }

    @Override
    public String createWherePart(SearchRelDataPermissions searchRelDataPermissions, boolean andPrefix, Long currentUserId) {
        if (searchRelDataPermissions == null || currentUserId == null) {
            return "";
        }
        StringBuilder sb = new StringBuilder();
        boolean more = andPrefix;
        for (Map.Entry<String, RelDataPermissions> columnRelDataPermissions : searchRelDataPermissions.getRelDataPermissions().entrySet()) {
            if (more) {
                sb.append(" AND ");
            }
            sb.append("t.");
            sb.append(columnRelDataPermissions.getKey());
            sb.append(" IN (");
            sb.append(getAuthIdListStr(columnRelDataPermissions.getValue(), currentUserId));
            sb.append(")");
            more = true;
        }
        return sb.toString();
    }

    @Override
    public void afterMethodInvoke(String changeAop) {
        if (changeAopToCacheKeys == null) {
            return;
        }
        List<String> keys = changeAopToCacheKeys.get(changeAop);
        if (keys != null) {
            for (String key : keys) {
                Objects.requireNonNull(redisTemplate.keys(key + ":*")).forEach(k -> redisTemplate.delete(k));
            }
        }
    }

    private String getFinalCacheKey(String group, String key) {
        return String.format("RDP:%s:%s", group, key);
    }

    private String getAuthIdListStr(RelDataPermissions relDataPermissions, Long currentUserId) {
        String key = String.format("%s:%d", getFinalCacheKey(relDataPermissions.getCacheGroup(), relDataPermissions.getCacheKey()), currentUserId);
        Object o = redisTemplate.opsForValue().get(key);
        if (o == null) {
            List<Long> values = readIdList(relDataPermissions, currentUserId);
            if (values.isEmpty()) {
                o = "0";
            } else {
                o = ListUtils.join(", ", values);
            }
            redisTemplate.opsForValue().set(key, o);
        }
        if (o instanceof String) {
            return (String)o;
        }
        return "0";
    }

    private List<Long> readIdList(RelDataPermissions relDataPermissions, Long currentUserId) {
        String sql = relDataPermissions.getSql().replaceAll("\\$\\{currentUserId}", currentUserId.toString());
        log.debug("rel data permission query at [{}:{}]: {}", relDataPermissions.getCacheGroup(), relDataPermissions.getCacheKey(), sql);
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
        return jdbcTemplate.queryForList(sql, Long.class);
    }

    private void loadJson() {
        try (InputStream inputStream = new ClassPathResource("rel-data-permissions.json").getInputStream()){
            ObjectMapper objectMapper = new ObjectMapper();
            objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
            RelDataPermissionsJson dpJson = objectMapper.readValue(inputStream, RelDataPermissionsJson.class);
            List<RelDataPermissions> relDataPermissions = dpJson.getRelDataPermissions();
            allRelDataPermissions = new HashMap<>();
            changeAopToCacheKeys = new HashMap<>();
            for (RelDataPermissions relDataPermission : relDataPermissions) {
                String cacheKey = getFinalCacheKey(relDataPermission.getCacheGroup(), relDataPermission.getCacheKey());
                if (relDataPermission.getChangeAop() != null) {
                    for (String changeAop : relDataPermission.getChangeAop()) {
                        List<String> strings = changeAopToCacheKeys.computeIfAbsent(changeAop, (k) -> new ArrayList<String>());
                        if (!strings.contains(cacheKey)) {
                            strings.add(cacheKey);
                        }
                    }
                }
            }
            for (RelDataPermissionsJson.SearchParamItem searchParam : dpJson.getSearchParams()) {
                SearchRelDataPermissions searchRelDataPermissions = new SearchRelDataPermissions();
                allRelDataPermissions.put(searchParam.getClassName(), searchRelDataPermissions);
                searchRelDataPermissions.setSearchParamClassName(searchParam.getClassName());
                searchRelDataPermissions.setRelDataPermissions(new HashMap<>());
                for (RelDataPermissionsJson.Item item : searchParam.getItems()) {
                    RelDataPermissions dataPermissions = ListUtils.find(relDataPermissions, dp -> item.getId().equals(dp.getCacheGroup() + "." + dp.getCacheKey()));
                    if (dataPermissions == null) {
                        log.error("关联数据权限 {} 未找到定义", item.getId());
                    }
                    searchRelDataPermissions.getRelDataPermissions().put(item.getColumn(), dataPermissions);
                }
            }
        } catch (Exception e) {
            log.error("读取关联数据权限数据失败!", e);
            throw new RuntimeException("读取关联数据权限数据失败！");
        }
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    }
}
